import { reactive, ref } from 'vue'
import type { Ref } from 'vue'
import { useBoolean } from './use-boolean'
import { useLoading } from './use-loading'

export type MaybePromise<T> = T | Promise<T>

export type ApiFn = (args: any) => Promise<unknown>

export type TableDataWithIndex<T> = T & { index: number }

export type TransformedData<T> = {
  data: TableDataWithIndex<T>[]
  pageNum: number
  pageSize: number
  pageCount: number
}

export type Transformer<T, Response> = (response: Response) => TransformedData<T>

export type TableConfig<A extends ApiFn, T> = {
  apiFn: A
  apiParams?: Parameters<A>[0]
  transformer: Transformer<T, Awaited<ReturnType<A>>>
  onFetched?: (transformed: TransformedData<T>) => MaybePromise<void>
  immediate?: boolean
}

export default function useHookTable<A extends ApiFn, T>(config: TableConfig<A, T>) {
  const { loading, startLoading, endLoading } = useLoading()
  const { bool: empty, setBool: setEmpty } = useBoolean()
  const { apiFn, apiParams, transformer, immediate = true } = config
  const searchParams: NonNullable<Parameters<A>[0]> = reactive({ ...apiParams })
  const data: Ref<T[]> = ref([])

  async function getData() {
    startLoading()
    const formattedParams = formatSearchParams(searchParams)
    const response = await apiFn(formattedParams)
    const transformed = transformer(response as Awaited<ReturnType<A>>)
    data.value = transformed.data
    setEmpty(transformed.data.length === 0)
    await config.onFetched?.(transformed)
    endLoading()
  }

  function formatSearchParams(params: Record<string, unknown>) {
    const formattedParams: Record<string, unknown> = {}

    Object.entries(params).forEach(([key, value]) => {
      if (value !== null && value !== undefined) {
        formattedParams[key] = value
      }
    })

    return formattedParams
  }

  function updateSearchParams(params: Partial<Parameters<A>[0]>) {
    Object.assign(searchParams, params)
  }

  function resetSearchParams() {
    Object.assign(searchParams, apiParams)
  }

  if (immediate) {
    getData()
  }

  return {
    loading,
    empty,
    data,
    getData,
    searchParams,
    updateSearchParams,
    resetSearchParams
  }
}
